﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
using Lidgren.Network;
using Ninject;
using Pixi.Logging;
using Pixi.Network;

namespace Pixi.Client.Network
{
    /// <summary>
    /// Represents a Client Transceiver for sending and recieving messages using UDP and/or TCP Channels.
    /// </summary>
    public class ClientTransceiver : IClientTransceiver
    {
		#region Properties and Variables
        ILogger _logger;

        private const long PING_INTERVAL = 4;

		private string _tcpServerIp = string.Empty;
		private int _tcpPort = 0;
		private string _udpServerIp = string.Empty;
		private int _udpPort = 0;
        private bool _udpKeepAliveRequestSent = false;
        private bool _tcpKeepAliveRequestSent = false;

        private short _applicationDomainId;
        private int _timeout = 5;

        private INetChannelFactory _netChannelFactory;
        private INetClientFactory _netClientFactory;

        private INetClient _udpClient = null;
        private INetClient _tcpNetClient = null;

        private long _connectTimeout = 50000000;
		private long _linkDeadTimeout = 30 * TimeSpan.TicksPerSecond;
		
        private long _startUdpConnect = 0;
        private string _userName = string.Empty;
        private string _password = string.Empty;
        private string _token = string.Empty;

        private int _oldSessionId = 0;
		private bool _isSwitchingServer = false;
        private bool _isAddingConnection = false;

        private string _appName = string.Empty;
        /// <summary>
        /// Returns or sets the appName value required when making connections to a Pixi server.
        /// </summary>
        public string AppName
        {
            get { return _appName; }
            set { _appName = value; }
        }

        private INetChannel _udpChannel = null;
        /// <summary>
        /// Returns the UDP channel object used for communicating with the server.
        /// </summary>
        public INetChannel UdpChannel
        {
            get { return _udpChannel; }
        }

        private INetChannel _tcpChannel = null;
        /// <summary>
        /// Returns or sets the TCP channel object used for communicating with the server
        /// </summary>
        public INetChannel TcpChannel
        {
            get { return _tcpChannel; }
            set { _tcpChannel = value; }
        }

        private Queue<Packet> _packetQueue = new Queue<Packet>();
        private object _packetSync = new object();

		//The time that the server last sent and we received a packet
		private long _timeLastReceived = 0;

        //Client Login Name
        private string _clientName = string.Empty;
        /// <summary>
        /// Returns or sets the Client Login name.
        /// </summary>
        public string ClientName
        {
            get { return _clientName; }
            set 
            { 
                _clientName = value;
                if (_udpChannel != null)
                {
                    _udpChannel.ChannelName = _clientName;
                }
                if (_tcpChannel != null)
                {
                    _tcpChannel.ChannelName = _clientName;
                }
            }
        }

        /// <summary>
        /// Returns the status of the UDP client connection.
        /// </summary>
        public NetConnectionStatus UdpStatus
        {
            get
            {
                if(_udpClient == null)
                    return NetConnectionStatus.None;
                else
                    return _udpClient.ServerConnectionStatus;
            }
        }

        /// <summary>
        /// Returns the status of the TCP client connection.
        /// </summary>
        public NetConnectionStatus TcpStatus
        {
            get
            {
                if (_tcpNetClient == null)
                    return NetConnectionStatus.None;
                else
                    return _tcpNetClient.ServerConnectionStatus;
            }
        }

        private string _errMessage = string.Empty;
        /// <summary>
        /// Returns any error messages generated when trying to start the Client Transceiver.
        /// </summary>
        public string ErrMessage
        {
            get { return _errMessage; }
        }

		
		#endregion Properties and Variables

        /// <summary>
        /// Initializes a new instance of the ClientTransceiver class using the specified 
        /// netClientFactory, netChannelFactory and logger.
        /// </summary>
        /// <param name="netClientFactory">INetClientFactory factory object used for creating NetClients</param>
        /// <param name="netChannelFactory">INetChannelFactory factory object used for creating NetChannels</param>
        /// <param name="logger">Logger used for logging messages</param>
        public ClientTransceiver(INetClientFactory netClientFactory, 
            INetChannelFactory netChannelFactory, [Named("Logger")] ILogger logger)
        {
            _logger = logger;
            _netChannelFactory = netChannelFactory;
            _netClientFactory = netClientFactory;
        }

        #region Server Business
        /// <summary>
        /// Starts the Client Transceiver's TCP connection.
        /// </summary>
        /// <param name="serverIp">IP Address of the server host we are connecting to.</param>
        /// <param name="port">Port of the server host we are connecting to.</param>
        /// <param name="timeout">Timeout value forthe connecton</param>
        /// <returns>boolean value representing success or failure.</returns>
        public bool StartTcp(string serverIp, int port, int timeout)
        {
            if (timeout > 0)
                _timeout = timeout;

            if (String.IsNullOrWhiteSpace(_appName))
                throw new Exception("Appname was not set in Client Transceiver.  Appname is required for this object.");

            //Get the ip address from the host
            if (serverIp.ToLower() == "localhost")
                serverIp = Dns.GetHostName();

            //IPHostEntry hostEntry = Dns.GetHostEntry(serverIp);

            //_tcpServerIp = hostEntry.AddressList[0].ToString();

			//Save them back for a retry later
			_tcpPort = port;		
            
        	 bool retVal = true;

            _tcpNetClient = _netClientFactory.CreateClient(NetClientType.TCP);

            if (!_tcpNetClient.Connect(_appName, serverIp, _tcpPort, _timeout, _logger))
            {
                _errMessage = _tcpNetClient.ErrMessage;

                return false;
            }

            _tcpChannel = _netChannelFactory.CreateChannel(_tcpNetClient.ServerConnection, _logger);
            _tcpChannel.ApplicationDomainId = _applicationDomainId;

            //Start it off by requesting a token
            Packet tokenPacket = _tcpChannel.CreatePacket((byte)SystemMessageClass.System, (short)SystemMessageType.TokenRequest, NetDeliveryMethod.ReliableUnordered);

            _tcpChannel.AddPacket(tokenPacket);
            //_tcpChannel.Send();
            _tcpChannel.State = ChannelState.Tokenizing;
            _timeLastReceived = DateTime.Now.Ticks;

            //This assumes that if we started the UDP connection first, we already logged in on that connection
            if (_udpChannel != null)
                _isAddingConnection = true;

            return retVal;
        }

        /// <summary>
        /// Starts the Client Transceiver's UDP connection.
        /// </summary>
        /// <param name="serverIp">IP Address of the server host we are connecting to.</param>
        /// <param name="port">Port of the server host we are connecting to.</param>
        /// <param name="timeout">Timeout value forthe connecton</param>
        /// <returns>boolean value representing success or failure.</returns>
        public bool StartUdp(string serverIp, int port, int timeout)
        {
            if (timeout > 0)
                _timeout = timeout;

            if (String.IsNullOrWhiteSpace(_appName))
                throw new Exception("Appname was not set in Client Transceiver.  Appname is required for this object.");

            //Save them back for a retry later
			_udpServerIp = serverIp;
			_udpPort = port;
            
        	 try
        	 {
	
	            // set up network
                 _udpClient = _netClientFactory.CreateClient(NetClientType.UDP);

                 if (!_udpClient.Connect(_appName, serverIp, port, timeout, _logger))
                 {
                     _errMessage = _udpClient.ErrMessage;
                     return false;
                 }


                _startUdpConnect = DateTime.Now.Ticks;

                _udpChannel = _netChannelFactory.CreateChannel(_udpClient.ServerConnection, _logger);

                _udpChannel.ApplicationDomainId = _applicationDomainId;

	            Packet tokenPacket = _udpChannel.CreatePacket((byte)SystemMessageClass.System, (short)SystemMessageType.TokenRequest, NetDeliveryMethod.ReliableUnordered);
	            _udpChannel.AddPacket(tokenPacket);
                //_udpChannel.Send();
                _udpChannel.State = ChannelState.Tokenizing;
                _timeLastReceived = DateTime.Now.Ticks;

                //This assumes that if we started the TCP connection first, we already logged in on that connection
                if (_tcpChannel != null)
                    _isAddingConnection = true;

                 return true;
        	 }
        	 catch(Exception ex)
        	 {
                 _errMessage = ex.Message;
            	return false;       	    	
        	 }
        }
		
        /// <summary>
        /// Stops the Client Transceiver shutting down the UDP and TCP client connections.
        /// </summary>
		public void Stop()
		{
			if(_udpClient != null && _udpClient.NetStatus == NetPeerStatus.Running)
			{
				_udpClient.Shutdown("Bye");
				_udpClient = null;
				_udpChannel = null;
			}

            if (_tcpNetClient != null && _tcpNetClient.NetStatus == NetPeerStatus.Running)
			{
				_tcpNetClient.Shutdown("Bye");
				_tcpNetClient = null;
				_tcpChannel = null;
			}
		}
        #endregion Server Business

        /// <summary>
        /// Reads the next Packet from the packet queue.
        /// </summary>
        /// <returns>Packet object</returns>
        public Packet ReadPacket()
        {
            Packet retPacket = null;
            if (_packetQueue.Count > 0)
            {
                lock (_packetSync)
                {
                    retPacket = _packetQueue.Dequeue();
                }
            }

            return retPacket;
        }
        
        /// <summary>
        /// Accepts the username and password and starts the login process sending the login request
        /// to the server.
        /// </summary>
        /// <param name="userName">Username for logging in.</param>
        /// <param name="password">Password used for logging in.</param>
        /// <param name="useUdp">boolean indicating whether to use UDP for logging in.</param>
        public void Login(string userName, string password, bool useUdp)
        {
            _userName = userName;
            _password = password;
			 INetChannel channel = null;
             float now = (float)NetTime.Now;

            //try to use the right channel if it is available
             if (useUdp)
             {
                 if (_udpChannel == null)
                     channel = _tcpChannel;
                 else
                     channel = _udpChannel;
             }
             else
             {
                 if (_tcpChannel == null)
                     channel = _udpChannel;
                 else
                     channel = _tcpChannel;
             }
            
            //Create packet to start logging in process
            if(channel != null)
            {
            	if (channel.State == ChannelState.None || channel.State == ChannelState.Tokenized 
                    || channel.State == ChannelState.NotLoggedIn || channel.State == ChannelState.Tokenizing)
                {
	                Packet loginRequestPacket = channel.CreatePacket((byte)SystemMessageClass.System, 
                        (short)SystemMessageType.LoginRequest, NetDeliveryMethod.ReliableUnordered);

	                loginRequestPacket.Payload = userName;
                    channel.AddPacket(loginRequestPacket);
                    //channel.Send();
                }
                else
                {
	                //Add incoming message Already Logged in.
	                Packet loginErrPacket = new Packet("", (byte)SystemMessageClass.System,
                        (short)SystemMessageType.Exception, now, NetDeliveryMethod.ReliableUnordered);

	                loginErrPacket.Payload = "Already Logged In!";
	            	
                    lock( _packetSync)
	            		_packetQueue.Enqueue(loginErrPacket);
	                return;
            	 }
            }
            else
            {
	            //Add incoming message not intialized.
	            Packet initErrPacket = new Packet("", (byte)SystemMessageClass.System, 
                    (short)SystemMessageType.Exception, now, NetDeliveryMethod.ReliableUnordered);

	            initErrPacket.Payload = "Cannot log in!  Channel is not initialized yet!";
	        	 lock( _packetSync)
	        		_packetQueue.Enqueue(initErrPacket);
	            return;
	
            }
        }

        /// <summary>
        /// Gets the statistics associated with this Transceiver.
        /// </summary>
        /// <returns>Read only collection of StatisticGroup objects</returns>
        public ReadOnlyCollection<StatisticGroup> GetStatistics()
        {
            List<StatisticGroup> statisticGroups = new List<StatisticGroup>();

            //Get transciever vital stats
            StatisticGroup transGroup = new StatisticGroup("Client Transceiver", (short)StatisticTemplate.ClientTranscieverStatistics);
            transGroup.AddStatistic("Application Name", this._appName);
            transGroup.AddStatistic("Error Message", this._errMessage);
            transGroup.AddStatistic("Packet Queue Count", this._packetQueue.Count.ToString());
            transGroup.AddStatistic("User Name", this._userName);
            transGroup.AddStatistic("Password", this._password);
            transGroup.AddStatistic("Token", this._token);

            double averageProcessingTime = 0;
            long channelCount = 0;
            if (_tcpChannel != null)
            {
                averageProcessingTime += _tcpChannel.AverageProcessingTime;
                channelCount++;
            }

            if (_udpChannel != null)
            {
                averageProcessingTime += _udpChannel.AverageProcessingTime;
                channelCount++;
            }

            if (channelCount > 0)
                averageProcessingTime = averageProcessingTime / channelCount;

            transGroup.AddStatistic("Average Processing Time", averageProcessingTime.ToString());

            //TCP Stats
            transGroup.AddStatistic("TCP Connection", this._tcpChannel != null ? "true" : "false");
            transGroup.AddStatistic("TCP Connection State", this._tcpChannel != null ? this._tcpChannel.State.ToString() : "none");
            transGroup.AddStatistic("TCP Server IP", this._tcpServerIp);
            transGroup.AddStatistic("TCP Port", this._tcpPort.ToString());
            transGroup.AddStatistic("TCP Packets Received", !this._tcpNetClient.IsConnected ? "0" : this._tcpNetClient.PacketsReceived.ToString());
            transGroup.AddStatistic("TCP Packets Sent", this._tcpChannel.PacketsSent.ToString());
            transGroup.AddStatistic("TCP Incoming Messages in Queue", !this._tcpNetClient.IsConnected ? "0" : this._tcpNetClient.MessageQueueCount.ToString());
            transGroup.AddStatistic("TCP Outgoing Messages in Queue", this._tcpChannel.TotalPacketQueueCount.ToString());
            transGroup.AddStatistic("TCP Outgoing Message Pre-Token Count", this._tcpChannel.PreTokenQueueCount.ToString());
            transGroup.AddStatistic("TCP Outgoing Message Pre-Login Count", this._tcpChannel.PreLoginQueueCount.ToString());
            transGroup.AddStatistic("TCP Outgoing Message Post-Login Count", this._tcpChannel.PostLoginQueueCount.ToString());

            //UDP Stats
            transGroup.AddStatistic("UDP Connection", this._udpClient.IsConnected ? "true" : "false");
            transGroup.AddStatistic("UDP Connection State", this._udpChannel.State.ToString());
            transGroup.AddStatistic("UDP Server IP", this._udpServerIp);
            transGroup.AddStatistic("UDP Port", this._udpPort.ToString());
            transGroup.AddStatistic("UDP Packets Received", this._tcpNetClient.PacketsReceived.ToString());
            transGroup.AddStatistic("UDP Packets Sent", this._tcpChannel.PacketsSent.ToString());
            transGroup.AddStatistic("UDP Incoming Messages in Queue", this._udpClient.MessageQueueCount.ToString());
            transGroup.AddStatistic("UDP Outgoing Messages in Queue", this._udpChannel.TotalPacketQueueCount.ToString());
            transGroup.AddStatistic("UDP Outgoing Message Pre-Token Count", this._udpChannel.PreTokenQueueCount.ToString());
            transGroup.AddStatistic("UDP Outgoing Message Pre-Login Count", this._udpChannel.PreLoginQueueCount.ToString());
            transGroup.AddStatistic("UDP Outgoing Message Post-Login Count", this._udpChannel.PostLoginQueueCount.ToString());
            statisticGroups.Add(transGroup);

            return statisticGroups.AsReadOnly();
        }

        #region Packet Handling
        public void SwitchServer(string host, int port, short applicationDomainId, string originalToken, bool isToServerUdp, bool isFromServerUdp)
        {
            _applicationDomainId = applicationDomainId;

        	INetChannel channel = null;
        	if(isFromServerUdp)
        		channel = _udpChannel;
        	else
        		channel = _tcpChannel;

            _oldSessionId = channel.SessionId;

        	 //Save the original token
        	 _token = originalToken;
        	 
            //if (_packetProcessTimer != null)
            //{
            //    _packetProcessTimer.Change(Timeout.Infinite, Timeout.Infinite);
            //    _packetProcessTimer.Dispose();
            //    _packetProcessTimer = null;
            //}

            _isSwitchingServer = true;
            channel.Disconnect("Switching Servers");
            if(isToServerUdp)
            	StartUdp(host, port, _timeout);
            else
            	StartTcp(host, port, _timeout);

        }

        /// <summary>
        /// Method that should be called periodically to send pending messages and process incoming messages.
        /// This method also checks to see if we are linkdead.
        /// </summary>
        public void Update()
        {
            try
            {
                if(_udpChannel != null)
                    _udpChannel.Send();

                if(_tcpChannel != null)
                    _tcpChannel.Send();

                //Process incomming messages
                ProcessUDPMessage();
                ProcessTCPMessage();

				//Check to make sure we are not link dead if not a direct connection
			    CheckLinkDead();
            }
            catch (Exception e)
            {
                _logger.Error("ClientTransceiver.PacketTimerProcess", e);
            }
        }

        private void ProcessUDPMessage()
        {
            if (_udpClient == null)
                return;
			 
            //check to see if the connection has timed out
            if(_startUdpConnect > 0)
            {
                if (DateTime.Now.Ticks - _startUdpConnect > _connectTimeout && _udpClient.NetStatus != NetPeerStatus.Running)
                {
                    _udpClient.Disconnect("Connection Timeout");
                    Packet packet = _udpChannel.CreatePacket((byte)SystemMessageClass.System, 
                        (short)SystemMessageType.ConnectionTimeout, NetDeliveryMethod.ReliableUnordered);

                    lock (_packetSync)
                        _packetQueue.Enqueue(packet);
                    _startUdpConnect = 0;
                    return;
                }
                else if (_udpClient.NetStatus == NetPeerStatus.Running)
                    _startUdpConnect = 0;
            }
            
            INetMessage im;
            while ((im = _udpClient.ReadMessage()) != null)
            {
                _logger.Debug("Received UDP Message in the " + _appName + " Client Transceiver :" + im.MessageType.ToString());
                switch (im.MessageType)
                {
                    case NetMessageType.StatusChanged:
                        byte status = im.ReadByte();
                        string reason = im.ReadString();
                        if(status == (byte)NetConnectionStatus.InitiatedConnect)
                        {
                        	_startUdpConnect = DateTime.Now.Ticks;
                        }
                        else if(status == (byte)NetConnectionStatus.Connected)
                        {
                       	    _startUdpConnect = 0;
                        }
                        else if(status == (byte)NetConnectionStatus.Disconnected)
                        {
                        	Packet packet = _udpChannel.CreatePacket((byte)SystemMessageClass.System, 
                                (short)SystemMessageType.ClientDisconnected, NetDeliveryMethod.ReliableUnordered);

                            packet.Payload = reason;
		            		lock(_packetSync)
		            			_packetQueue.Enqueue(packet);
		            		_startUdpConnect = 0;
		            		return;
		
                        }
                        break;

                    default:
                        HandlIMMessage(im, _udpChannel);
                        break;
                }
            }
        }

        private void ProcessTCPMessage()
        {
            if (_tcpNetClient == null)
                return;

            if (_tcpChannel.State == ChannelState.Disconnected)
                return;

            INetMessage im;
            while ((im = _tcpNetClient.ReadMessage()) != null)
            {
                HandlIMMessage(im, _tcpChannel);
            }

            //Check for ping time while a lull in the action to synchronize client with server
            float now = (float)NetTime.Now;
            float lastPingTime = (_tcpNetClient.ServerConnection as IReceiveMessage).SentPingTime;

            if (now > lastPingTime + PING_INTERVAL)
            {
                _tcpChannel.SendPing();
            }

        }

        private void HandlIMMessage(INetMessage im, INetChannel channel)
        {
            switch (im.MessageType)
            {
                case NetMessageType.DebugMessage:
                case NetMessageType.VerboseDebugMessage:
                    _logger.Debug(im.ReadString());
                    break;
                case NetMessageType.InfoMessage:
                    _logger.Info(im.ReadString());
                    break;
                case NetMessageType.WarningMessage:
                    _logger.Warn(im.ReadString());
                    break;
                case NetMessageType.ErrorMessage:
                    _logger.Error(im.ReadString());
                    break;
                case NetMessageType.Data:
                    HandlePacket(im.ReadBytes(im.LengthBytes), channel);
                    break;
            }
        }

        private void HandlePacket(byte[] buffer, INetChannel channel)
        {
 			_timeLastReceived = DateTime.Now.Ticks;
            Packet packet = null;
            packet = new Packet(buffer, channel.Token);
            packet.IsUDP = channel.IsUDPChannel;

            float now = channel.GetServerTime();

            if(packet.MessageType != (short)SystemMessageType.Ping &&
                packet.MessageType != (short)SystemMessageType.KeepAlive &&
                packet.MessageType != (short)SystemMessageType.KeepAliveRequest)
                _logger.Debug("Client Transceiver Received Packet: " + packet.ToString());

            if (channel.State == ChannelState.Tokenizing)
            {
                if (DateTime.Now.Ticks - channel.TimeLastSent > (_timeout * TimeSpan.TicksPerSecond))
                {
                    //To much time to tokenize - report error
                    channel.Disconnect("Timeout");
                    Packet errPacket = new Packet("", (byte)SystemMessageClass.System, (short)SystemMessageType.Exception, now, NetDeliveryMethod.ReliableUnordered);
                    errPacket.Payload = "Connection timed out.";
                    lock (_packetSync)
                        _packetQueue.Enqueue(errPacket);

                }
            }

            packet = new Packet(buffer, channel.Token);
            packet.ReceivedTime = now;

            if (packet.MessageClass == (byte)SystemMessageClass.System)
            {
                if (packet.MessageType == (short)SystemMessageType.TokenReturn)
                {
                    channel.AssignSessionId(packet.TargetID);
                    if (_isSwitchingServer || _isAddingConnection)
                        channel.Token = _token;
                    else
                        channel.Token = packet.Payload.ToString();
                    channel.State = ChannelState.Tokenized;

                    //Send the switchserver request
                    if (_isSwitchingServer)
                    {
                        Packet switchPacket = channel.CreatePacket((byte)SystemMessageClass.System, 
                            (short)SystemMessageType.SwitchServerRequest, NetDeliveryMethod.ReliableOrdered);
                        switchPacket.Payload = _oldSessionId;

                        channel.AddPacket(switchPacket);
                        //channel.Send();
                    }
                    else if (_isAddingConnection)
                    {
                        INetChannel otherChannel = null;
                        if (channel.IsUDPChannel)
                            otherChannel = _tcpChannel;
                        else
                            otherChannel = _udpChannel;

                        Packet addConnectionPacket = channel.CreatePacket((byte)SystemMessageClass.System, 
                            (short)SystemMessageType.AddServerConnectionRequest, NetDeliveryMethod.ReliableOrdered);
                        addConnectionPacket.Payload = otherChannel.SessionId;

                        channel.AddPacket(addConnectionPacket);
                        //channel.Send();
                    }
                }
                else
                {
                    switch (packet.MessageType)
                    {

                        case (short)SystemMessageType.LoginSuccess:
                            channel.State = ChannelState.LoggedIn;
                            break;
                        case (short)SystemMessageType.LoginChallenge:
                            //Return the LoginReturn packet
                            SendLoginChallengeResponse(packet, channel);
                            channel.State = ChannelState.LoggingIn;
                            return;
                        case (short)SystemMessageType.LoginError:
                            channel.State = ChannelState.NotLoggedIn;
                            break;
                        case (short)SystemMessageType.AddServerConnectionAccept:
                            INetChannel otherChannel = channel.IsUDPChannel ? _tcpChannel : _udpChannel;
                            channel.State = ChannelState.LoggedIn;
                            channel.AssignSessionId(otherChannel.SessionId);
                            channel.Token = otherChannel.Token;
                            break;
                        case (short)SystemMessageType.AddServerConnectionChallenge:
                            //Return the LoginReturn packet
                            SendAddConnectionChallengeResponse(packet, channel);
                            channel.State = ChannelState.LoggingIn;
                            return;
                        case (short)SystemMessageType.AddServerConnectionDenied:
                            channel.State = ChannelState.NotLoggedIn;
                            break;
                        case (short)SystemMessageType.ConnectionTimeout:
                            channel.State = ChannelState.None;
                            channel.Disconnect("Connection Timed Out");
                            break;
                        case (short)SystemMessageType.SwitchServerDenied:
                            channel.Disconnect("Switch Server Denied.");
                            channel.State = ChannelState.None;
                            break;
                        case (short)SystemMessageType.SwitchServerChallenge:
                            SendSwitchServerChallengeResponse(packet, channel);
                            return;
                        case (short)SystemMessageType.SwitchServerAccept:
                            channel.State = ChannelState.LoggedIn;
                            break;
                        case (short)SystemMessageType.KeepAlive: //Send a ping if receiving keep alive
                            _udpKeepAliveRequestSent = false;
                            _tcpKeepAliveRequestSent = false;
                            _tcpNetClient.ServerConnection.SendPing();
                            return;
                    }

                }
            }
            lock (_packetSync)
            {
                _packetQueue.Enqueue(packet);
            }
        }
        #endregion Packet Handling

        /// <summary>
        /// Checks to see if this Tranceiver is linkdead.
        /// </summary>
        /// <returns>Boolean value indicating whether the Transceiver is linkdead.</returns>
        public bool IsLinkDead()
        {
            bool retVal = true;
            long lastReceived = GetLastReceived();

            if ((DateTime.Now.Ticks - lastReceived) / TimeSpan.TicksPerSecond < _linkDeadTimeout)
                retVal = false;

            return retVal;
        }

        /// <summary>
        /// Gets a value indicating how many ticks have passed since the last
        /// time we received a message.
        /// </summary>
        /// <returns>Long value representing the number of ticks since the last received message.</returns>
        public long GetLastReceived()
        {
            long tcpLastReceived = 0;
            long udpLastReceived = 0;
            if (_tcpChannel != null)
                tcpLastReceived = _tcpChannel.LastReceivedTime;

            if (_udpChannel != null) // &&
                udpLastReceived = _udpChannel.LastReceivedTime;

            return System.Math.Max(tcpLastReceived, udpLastReceived);
        }

        private void SendLoginChallengeResponse(Packet packet, INetChannel channel)
        {
        	LoginChallenge lc = new LoginChallenge((byte[])packet.Payload);
			string salt = lc.Salt;
			string challenge = lc.ChallengeSalt;
			string pwresult = NetworkUtility.OneWayHash(_password, salt);
			string challengeResult = NetworkUtility.OneWayHash(pwresult, challenge);

			Packet lcPacket = channel.CreatePacket((byte)SystemMessageClass.System, (short)SystemMessageType.LoginResponse, Lidgren.Network.NetDeliveryMethod.ReliableUnordered);
			lcPacket.Payload = challengeResult;
			channel.AddPacket(lcPacket);
            //channel.Send();
            channel.Token = NetworkUtility.OneWayHash(challengeResult, channel.Token);
        }

        private void SendAddConnectionChallengeResponse(Packet packet, INetChannel channel)
        {
            INetChannel otherChannel = null;
            if (channel.IsUDPChannel)
                otherChannel = _tcpChannel;
            else
                otherChannel = _udpChannel;

            string salt = (string)packet.Payload;
            string challengeResult = NetworkUtility.OneWayHash(otherChannel.Token, salt);

            channel.Token = otherChannel.Token;
            _token = otherChannel.Token;

            Packet challengeResultPacket = channel.CreatePacket((byte)SystemMessageClass.System, (short)SystemMessageType.AddServerConnectionChallengResponse, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
            challengeResultPacket.Payload = challengeResult;
            channel.AddPacket(challengeResultPacket);
            //channel.Send();
        }
 
        private void SendSwitchServerChallengeResponse(Packet packet, INetChannel channel)
        {
        	string challenge = packet.Payload.ToString();
        	string challengeResult = NetworkUtility.OneWayHash(_token, challenge);
        	
        	//Use same token as was assigned by the login server
        	channel.Token = _token;
        	
        	Packet scPacket = channel.CreatePacket((byte)SystemMessageClass.System, (short)SystemMessageType.SwitchServerChallengeResponse, Lidgren.Network.NetDeliveryMethod.ReliableUnordered);
			scPacket.Payload = challengeResult;
			channel.AddPacket(scPacket);
            //channel.Send();
        }
        
		private void CheckLinkDead()
		{
            float now = (float)NetTime.Now;
			//Add incoming message not intialized.
            //Give it 10 seconds before declaring linkdead
            _timeLastReceived = GetLastReceived();

			if(DateTime.Now.Ticks - _timeLastReceived > _linkDeadTimeout + (10 * TimeSpan.TicksPerSecond))
			{
                if (IsLinkDead())
                {
                    _tcpChannel.Disconnect("Link Dead");
                    _udpChannel.Disconnect("Link Dead");

                    Packet linkDeadPacket = new Packet("", (byte)SystemMessageClass.System, 
                        (short)SystemMessageType.LinkDead, now, NetDeliveryMethod.ReliableUnordered);
                    linkDeadPacket.Payload = "Client is LinkDead.";
                    lock (_packetSync)
                        _packetQueue.Enqueue(linkDeadPacket);
                }
                _udpKeepAliveRequestSent = false;
                _tcpKeepAliveRequestSent = false;

			}
            else if (DateTime.Now.Ticks - _timeLastReceived > _linkDeadTimeout)
            {
                Packet keepAlivePacket = null;

                if (_tcpChannel != null && _tcpChannel.State == ChannelState.LoggedIn && !_tcpKeepAliveRequestSent)
                {
                    //send the keepalive request
                    keepAlivePacket = _tcpChannel.CreatePacket((byte)SystemMessageClass.System, (short)SystemMessageType.KeepAliveRequest, NetDeliveryMethod.ReliableUnordered);
                    _tcpChannel.AddPacket(keepAlivePacket);
                    //_tcpChannel.Send();
                    _tcpKeepAliveRequestSent = true;
                }
                if (_udpChannel != null && _udpChannel.State == ChannelState.LoggedIn && !_udpKeepAliveRequestSent)
                {
                    keepAlivePacket = _udpChannel.CreatePacket((byte)SystemMessageClass.System, (short)SystemMessageType.KeepAliveRequest, NetDeliveryMethod.ReliableUnordered);
                    _udpChannel.AddPacket(keepAlivePacket);
                    //_udpChannel.Send();
                    _udpKeepAliveRequestSent = true;
                }

            }
		}
    }
}
